import hashlib
import hmac
import http.client
import json
import logging
import time
from datetime import datetime

from config.env import KylinConfig

logger = logging.getLogger(__name__)

# 从环境配置获取Kylin配置
config = KylinConfig  # 现在是字典对象，使用get方法访问配置项


class CaptchaRecognizer:
    """
    验证码识别工具类 - 基于腾讯云API实现
    参照cfmmc_crawler.py中的实现进行优化
    """

    @staticmethod
    def _sign(key, msg):
        """
        生成HMAC签名
        参照cfmmc_crawler.py的实现
        """
        return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest()

    @staticmethod
    def recognize_captcha(
        image_data: str,
        whitelist: str = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
        min_length: int = 6,
        max_length: int = 6,
        max_attempts: int = 3,
    ) -> str:
        """
        使用腾讯云OCR API识别验证码
        参照cfmmc_crawler.py中的_recognize_verification_code方法实现

        :param image_data: 验证码图片数据（base64编码字符串）
        :param whitelist: 允许的字符集（用于结果过滤）
        :param min_length: 最小长度要求（默认6位）
        :param max_length: 最大长度要求（默认6位）
        :param max_attempts: 最大尝试次数（默认3次）
        :return: 识别出的验证码
        :raises Exception: 当验证码识别失败时
        """
        # 获取腾讯云API配置
        tencent_config = config.get('TENCENT_CLOUD', {})
        secret_id = tencent_config.get('secret_id', '')
        secret_key = tencent_config.get('secret_key', '')

        # 验证密钥配置
        if not secret_id or not secret_key:
            error_msg = '腾讯云API密钥未配置，请在kylin_config.py中设置'
            logger.error(error_msg)
            raise Exception(error_msg)

        # 重试机制
        for attempt in range(1, max_attempts + 1):
            try:
                logger.info(f'尝试 {attempt}/{max_attempts}: 开始通过腾讯云API识别验证码')

                # 配置腾讯云API参数 - 参照cfmmc_crawler.py
                service = "ocr"
                host = "ocr.tencentcloudapi.com"
                region = tencent_config.get('ocr_service_region', '')
                version = tencent_config.get('ocr_service_version', '2018-11-19')
                action = "GeneralAccurateOCR"
                algorithm = "TC3-HMAC-SHA256"
                
                # 直接使用base64编码的图片数据 - 参照cfmmc_crawler.py的ImageBase64方式
                # 检查是否为Data URL格式，如果是则提取base64部分
                if image_data.startswith('data:image/'):
                    try:
                        base64_image = image_data.split(',')[1]
                    except (IndexError, ValueError) as e:
                        raise Exception(f'Data URL格式无效: {str(e)}')
                else:
                    # 假设已经是base64编码
                    base64_image = image_data
                
                # 构建请求体 - 使用ImageBase64参数，参照cfmmc_crawler.py
                payload = json.dumps({"ImageBase64": base64_image, "EnableDetectText": False})

                # 时间戳生成 - 参照cfmmc_crawler.py
                timestamp = int(time.time())
                date = datetime.utcfromtimestamp(timestamp).strftime("%Y-%m-%d")

                # 步骤 1：拼接规范请求串 - 参照cfmmc_crawler.py
                http_request_method = "POST"
                canonical_uri = "/"
                canonical_querystring = ""
                ct = "application/json; charset=utf-8"
                canonical_headers = "content-type:%s\nhost:%s\nx-tc-action:%s\n" % (ct, host, action.lower())
                signed_headers = "content-type;host;x-tc-action"
                hashed_request_payload = hashlib.sha256(payload.encode("utf-8")).hexdigest()
                canonical_request = (
                    http_request_method + "\n" +
                    canonical_uri + "\n" +
                    canonical_querystring + "\n" +
                    canonical_headers + "\n" +
                    signed_headers + "\n" +
                    hashed_request_payload
                )

                # 步骤 2：拼接待签名字符串 - 参照cfmmc_crawler.py
                credential_scope = date + "/" + service + "/" + "tc3_request"
                hashed_canonical_request = hashlib.sha256(canonical_request.encode("utf-8")).hexdigest()
                string_to_sign = (
                    algorithm + "\n" +
                    str(timestamp) + "\n" +
                    credential_scope + "\n" +
                    hashed_canonical_request
                )

                # 步骤 3：计算签名 - 参照cfmmc_crawler.py
                secret_date = CaptchaRecognizer._sign(("TC3" + secret_key).encode("utf-8"), date)
                secret_service = CaptchaRecognizer._sign(secret_date, service)
                secret_signing = CaptchaRecognizer._sign(secret_service, "tc3_request")
                signature = hmac.new(secret_signing, string_to_sign.encode("utf-8"), hashlib.sha256).hexdigest()

                # 步骤 4：拼接 Authorization - 参照cfmmc_crawler.py
                authorization = (
                    algorithm + " " +
                    "Credential=" + secret_id + "/" + credential_scope + ", " +
                    "SignedHeaders=" + signed_headers + ", " +
                    "Signature=" + signature
                )

                # 步骤 5：构造并发起请求 - 参照cfmmc_crawler.py，timestamp保持为整数
                headers = {
                    "Authorization": authorization,
                    "Content-Type": "application/json; charset=utf-8",
                    "Host": host,
                    "X-TC-Action": action,
                    "X-TC-Timestamp": timestamp,
                    "X-TC-Version": version
                }
                if region:
                    headers["X-TC-Region"] = region

                # 发起HTTP请求
                conn = http.client.HTTPSConnection(host)
                conn.request("POST", "/", headers=headers, body=payload.encode("utf-8"))
                resp = conn.getresponse()

                # 读取响应内容
                response_data = resp.read()
                conn.close()

                # 检查响应状态
                if resp.status != 200:
                    error_msg = f'腾讯云API请求失败: {resp.status}, {response_data.decode("utf-8")}'
                    logger.error(error_msg)
                    if attempt >= max_attempts:
                        raise Exception(error_msg)
                    continue

                # 解析JSON响应
                try:
                    result = json.loads(response_data)
                except json.JSONDecodeError as e:
                    logger.error(f'JSON解析失败: {str(e)}')
                    if attempt >= max_attempts:
                        raise Exception(f'腾讯云API响应解析失败: {str(e)}')
                    continue

                # 参照cfmmc_crawler.py的响应处理
                if 'Response' not in result:
                    error_msg = f'腾讯云API响应结构异常: {result}'
                    logger.error(error_msg)
                    if attempt >= max_attempts:
                        raise Exception(error_msg)
                    continue
                
                # 检查是否存在错误响应
                if 'Error' in result['Response']:
                    error = result['Response']['Error']
                    error_msg = f'腾讯云API错误: {error.get("Code", "未知错误码")}, {error.get("Message", "未知错误信息")}'
                    logger.error(error_msg)
                    if attempt >= max_attempts:
                        raise Exception(error_msg)
                    continue

                # 提取识别结果
                if 'TextDetections' not in result['Response']:
                    logger.warning(f'尝试 {attempt}/{max_attempts}: 腾讯云API未返回识别结果')
                    if attempt >= max_attempts:
                        raise Exception('腾讯云API未返回识别结果')
                    continue

                text_detections = result['Response']['TextDetections']
                if not text_detections:
                    logger.warning(f'尝试 {attempt}/{max_attempts}: 腾讯云API未识别到文本')
                    if attempt >= max_attempts:
                        raise Exception('腾讯云API未识别到文本内容')
                    continue

                # 提取DetectedText字段并清理 - 参照cfmmc_crawler.py
                captcha_text = text_detections[0].get('DetectedText', '').strip()
                # 仅清理识别结果中的空格 - 与cfmmc_crawler.py保持一致
                captcha_text = captcha_text.replace(' ', '')
                logger.debug(f"尝试 {attempt}/{max_attempts}: 腾讯云API返回的识别结果: '{captcha_text}'")

                # 简化验证逻辑，与cfmmc_crawler.py保持一致
                # 只做基本验证，不进行复杂的白名单过滤
                if captcha_text and len(captcha_text) >= min_length:
                    logger.info(f'尝试 {attempt}/{max_attempts}: 验证码识别成功: {captcha_text}')
                    return captcha_text
                else:
                    logger.warning(
                        f"尝试 {attempt}/{max_attempts}: 验证码识别结果无效: '{captcha_text}'"
                    )

                    # 如果是最后一次尝试，抛出异常
                    if attempt >= max_attempts:
                        error_msg = f"验证码识别结果无效: '{captcha_text}'"
                        logger.error(error_msg)
                        raise Exception(error_msg)

                    # 短暂延迟后重试
                    time.sleep(0.5)

            except Exception as e:
                logger.error(f'尝试 {attempt}/{max_attempts}: 识别失败: {str(e)}')
                if attempt >= max_attempts:
                    raise
                time.sleep(1)  # 发生异常时等待更长时间再重试

        # 理论上不会到达这里，但为了代码完整性
        raise Exception('验证码识别失败：达到最大尝试次数')

    @staticmethod
    def is_valid_captcha(
        text: str,
        min_length: int = 6,
        max_length: int = 6,
        allowed_chars: str = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
    ) -> bool:
        """
        验证验证码格式是否有效

        :param text: 验证码文本
        :param min_length: 最小长度（默认6位）
        :param max_length: 最大长度（默认6位）
        :param allowed_chars: 允许的字符集
        :return: 是否有效
        """
        if not isinstance(text, str):
            return False

        if not (min_length <= len(text) <= max_length):
            return False

        return all(char in allowed_chars for char in text)


# 提供便捷的函数接口
def recognize_captcha(
    image_data: str,
    whitelist: str = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
    min_length: int = 6,
    max_length: int = 6,
    max_attempts: int = 3,
) -> str:
    """
    便捷的验证码识别函数 - 基于腾讯云API实现，支持重试机制
    参照cfmmc_crawler.py中的实现进行优化

    :param image_data: 验证码图片数据（base64编码字符串或Data URL）
    :param whitelist: 允许的字符集（用于结果过滤）
    :param min_length: 最小长度要求（默认6位）
    :param max_length: 最大长度要求（默认6位）
    :param max_attempts: 最大尝试次数（默认3次）
    :return: 识别出的验证码
    :raises Exception: 当验证码识别失败时
    """
    return CaptchaRecognizer.recognize_captcha(image_data, whitelist, min_length, max_length, max_attempts)
